package edu.upf.grupE.Video;

import java.awt.*;
import java.io.*;
import java.net.InetAddress;
import javax.media.*;
import javax.media.protocol.*;
import javax.media.protocol.DataSource;
import javax.media.format.*;
import javax.media.control.TrackControl;
import javax.media.rtp.*;


public class AVTransmit2 {

    private MediaLocator locator;
    private String ipAddress;
    private int portBase;

    private Processor processor = null;
    private RTPManager rtpMgrs[];
    private DataSource dataOutput = null;

    private Integer stateLock = new Integer(0);
    private boolean failed = false;

    /**
     * Constructor of the transmissor:
     * locator: Describes the location of the media content
     * ipAddress: Saves the destination ip address
     * port: Saves the destination port 
     */
    public AVTransmit2(MediaLocator locator,
			 String ipAddress,
			 String port) {

	this.locator = locator;
	this.ipAddress = ipAddress;
	Integer integer = Integer.valueOf(port);
	if (integer != null)
	    this.portBase = integer.intValue();
    }

    /**
     * This method starts the transmission and creates the processor
     * and the transmitter necessaries to communicate each other.
     */
    public synchronized void start() {
	// Create a processor for the specified media locator.
	String result = createProcessor();
	System.err.println(result);
	// Create an RTP session to transmit the output of the
	// processor to the specified IP address.
	createTransmitter();
	
	// Start the transmission running the processor.
	processor.start();
    }

    /**
     * This method stops the transmission if it was created before.
     * Remove all the information saved.
     */
    public void stop() {
        synchronized (this) {
            if (processor != null) {
                processor.stop();
                processor.close();
                processor = null;
                for (int i = 0; i < rtpMgrs.length; i++) {
                    rtpMgrs[i].removeTargets( "Session ended.");
                    rtpMgrs[i].dispose();
                }
            }
        }
    }

    /**
     * This method creates the processor activated on start()
     */
    private String createProcessor() {
        if (locator == null) {
            return "Locator is null";
        }

        DataSource ds;

        try {
            ds = javax.media.Manager.createDataSource(locator);
        } catch (Exception e) {
            return "Couldn't create DataSource";
        }

        // Try to create a processor to handle the input media locator
        try {
            processor = javax.media.Manager.createProcessor(ds);
        } catch (NoProcessorException npe) {
            return "Couldn't create processor";
        } catch (IOException ioe) {
            return "IOException creating processor";
        }

        // Wait to configure the processor
        boolean result = waitForState(processor, Processor.Configured);
        if (result == false)
            return "Couldn't configure processor";

        // Get the tracks from the processor
        TrackControl [] tracks = processor.getTrackControls();

        // Check if there is at least one track
        if (tracks == null || tracks.length < 1)
            return "Couldn't find tracks in processor";

        // Set the output data to RAW_RTP
        ContentDescriptor cd = new ContentDescriptor(ContentDescriptor.RAW_RTP);
        processor.setContentDescriptor(cd);

        Format supported[];
        Format chosen;
        boolean atLeastOneTrack = false;

        // Change the output content to the first supported format
        for (int i = 0; i < tracks.length; i++) {
            if (tracks[i].isEnabled()) {

            supported = tracks[i].getSupportedFormats();

            if (supported.length > 0) {
                if (supported[0] instanceof VideoFormat) {
                // Function to check the format of the video.
                    chosen = checkForVideoSizes(tracks[i].getFormat(),
                                supported[0]);
                } else
                chosen = supported[0];
                tracks[i].setFormat(chosen);
                System.err.println("Track " + i + " is set to transmit as:");
                System.err.println("  " + chosen);
                atLeastOneTrack = true;
            } else
                tracks[i].setEnabled(false);
            } else
            tracks[i].setEnabled(false);
       }

        if (!atLeastOneTrack)
            return "Couldn't set any of the tracks to a valid RTP format";

        // Call waitForState to realize the processor and maintain the
        // clock correctly.
        result = waitForState(processor, Controller.Realized);
        if (result == false)
            return "Couldn't realize processor";

        // Get the output data source of the processor
        dataOutput = processor.getDataOutput();

        return null;
    }


    /**
     * Creates transmitters for every session established.
     */
    private String createTransmitter() {

        PushBufferDataSource pbds = (PushBufferDataSource)dataOutput;
        PushBufferStream pbss[] = pbds.getStreams();

        rtpMgrs = new RTPManager[pbss.length];
        SessionAddress localAddr, destAddr;
        InetAddress ipAddr;
        SendStream sendStream;
        int port;
        
        for (int i = 0; i < pbss.length; i++) {
            try {
            rtpMgrs[i] = RTPManager.newInstance();

            // Creates another session to transmit the audio,
            // it is transmitted in a different port.
            port = portBase + 2*i;
            ipAddr = InetAddress.getByName(ipAddress);

            localAddr = new SessionAddress( InetAddress.getLocalHost(), port);
            destAddr = new SessionAddress( ipAddr, port);

            rtpMgrs[i].initialize(localAddr);
            rtpMgrs[i].addTarget(destAddr);

            System.err.println( "Created RTP session: " + ipAddress + " " + port);

            sendStream = rtpMgrs[i].createSendStream(dataOutput, i);
            sendStream.start();
            } catch (Exception  e) {
            return e.getMessage();
            }
        }

        return null;
    }


    /**
     * Check the video format and resizes it if is necessary
     */
    Format checkForVideoSizes(Format original, Format supported) {

        int width, height;
        Dimension size = ((VideoFormat)original).getSize();
        Format jpegFmt = new Format(VideoFormat.JPEG_RTP);

        if (supported.matches(jpegFmt)) {
            width = (size.width % 8 == 0 ? size.width :
                    (int)(size.width / 8) * 8);
            height = (size.height % 8 == 0 ? size.height :
                    (int)(size.height / 8) * 8);
        } else {
            return supported;
        }

        return (new VideoFormat(null,
                    new Dimension(width, height),
                    Format.NOT_SPECIFIED,
                    null,
                    Format.NOT_SPECIFIED)).intersects(supported);
    }

    Integer getStateLock() {
        return stateLock;
    }

    void setFailed() {
        failed = true;
    }

    private synchronized boolean waitForState(Processor p, int state) {
        p.addControllerListener(new StateListener());
        failed = false;

        if (state == Processor.Configured) {
            p.configure();
        } else if (state == Processor.Realized) {
            p.realize();
        }

        while (p.getState() < state && !failed) {
            synchronized (getStateLock()) {
            try {
                getStateLock().wait();
            } catch (InterruptedException ie) {
                return false;
            }
            }
        }

        if (failed)
            return false;
        else
            return true;
    }

    class StateListener implements ControllerListener {
        public void controllerUpdate(ControllerEvent ce) {
            // If there was an error during configure or
            // realize, the processor will be closed
            if (ce instanceof ControllerClosedEvent)
            setFailed();

            // All controller events, send a notification
            // to the waiting thread in waitForState method.
            if (ce instanceof ControllerEvent) {
            synchronized (getStateLock()) {
                getStateLock().notifyAll();
            }
            }
        }
    }

}
